#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sqlite3.h>
#include <assert.h>
#include <malloc.h>
#include <string.h>

static int createBlobTable(sqlite3 *db) {
    const char *zSql = "CREATE TABLE blobs(key TEXT PRIMARY KEY, value BLOB);";
    return sqlite3_exec(db, zSql, 0, 0, 0);
}

static int writeBlob(
                sqlite3 *db,
                const char *zKey,
                void *zBlob,
                int nBlob
                ) {

    const char *zSql = "INSERT INTO blobs(key, value) VALUES(?, ?)";
    sqlite3_stmt *pStmt;
    int rc;

    do {

        rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
        if(rc != SQLITE_OK) {
            return rc;
        }

        sqlite3_bind_text(pStmt, 1, zKey, -1, SQLITE_STATIC);
        sqlite3_bind_blob(pStmt, 2, zBlob, nBlob, SQLITE_STATIC);

        rc = sqlite3_step(pStmt);
        assert(rc != SQLITE_ROW);

        rc = sqlite3_finalize(pStmt);
    } while(rc == SQLITE_SCHEMA);

    return rc;
}

static int readBlob(
                sqlite3 *db,
                const char *zKey,
                unsigned char **pzBlob,
                int *pnBlob
                ) {

    const char *zSql = "SELECT value FROM blobs WHERE key = ?";
    sqlite3_stmt *pStmt;
    int rc;

    *pzBlob = 0;
    *pnBlob = 0;

    do {

        rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
        if(rc != SQLITE_OK) {
            return rc;
        }

        sqlite3_bind_text(pStmt, 1, zKey, -1, SQLITE_STATIC);

        rc = sqlite3_step(pStmt);
        if(rc == SQLITE_ROW) {
            *pnBlob = sqlite3_column_bytes(pStmt, 0);
            *pzBlob = (unsigned char *)malloc(*pnBlob);
            memcpy(*pzBlob, (void *)sqlite3_column_blob(pStmt, 0), *pnBlob);
        }

        rc = sqlite3_finalize(pStmt);

    } while(rc == SQLITE_SCHEMA);

    return rc;
}

static void freeBlob(unsigned char *zBlob) {
    free(zBlob);
}

static void databaseError(sqlite3* db) {
    int errcode = sqlite3_errcode(db);
    const char *errmsg = sqlite3_errmsg(db);
    fprintf(stderr, "Database error %d: %s\n", errcode, errmsg);
}

int main(int argc, char *argv[]) {

    sqlite3 *db;
    char const *zDatabase;
    char const *zFile;
    int isStore = 0;
    int fd;
    struct stat sStat;
    int nBlob;
    unsigned char *zBlob;

    if(argc != 4) {
        fprintf(stderr, "Usage: %s <db file> store | retrieve <filename>\n", argv[0]);
        return 1;
    }

    zDatabase = argv[1];
    zFile = argv[3];
    if(argv[2][0] == 's' || argv[2][0] == 'S') {
        isStore = 1;
    }

    sqlite3_open(zDatabase, &db);
    if(SQLITE_OK != sqlite3_errcode(db)) {
        databaseError(db);
        return 1;
    }

    if(isStore) {

        createBlobTable(db);

        fd = open(zFile, O_RDONLY);
        if(fd < 0) {
            perror("open");
            return 1;
        }

        if(0 != fstat(fd, &sStat)) {
            perror("fstat");
            return 1;
        }
        nBlob = sStat.st_size;

        zBlob = (unsigned char *)malloc(nBlob);
        if(nBlob != read(fd, zBlob, nBlob)) {
            perror("read");
            return 1;
        }
        close(fd);

        if(SQLITE_OK != writeBlob(db, zFile, zBlob, nBlob)) {
            databaseError(db);
            return 1;
        }
        free(zBlob);

    } else {

        fd = open(zFile, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
        if(fd < 0) {
            perror("open");
            return 1;
        }

        if(SQLITE_OK != readBlob(db, zFile, &zBlob, &nBlob)) {
            databaseError(db);
            return 1;
        }

        if(!zBlob) {
            fprintf(stderr, "No such database entry: %s\n", zFile);
            return 1;
        }

        if(nBlob != write(fd, zBlob, nBlob)) {
            perror("write");
            return 1;
        }
        close(fd);

        freeBlob(zBlob);
    }

    return 0;
}


